﻿using System;
using System.IO;
using System.Linq;
using System.Text;

namespace PeVerResEditor
{
    // 不太正规的方式
    class Program
    {
        static void Main(string[] args)
        {
            /*
             * 
             * -f   file version        1.2.3
             * -p   product version     pv1
             * -c   company name        公司
             * -d   description         文件说明
             * -n   product name        产品名称
             * -l   legal               版权
             * -t   Trademarks          商标
             * 
             */

            if ((args.Length & 1) != 1)
            {
                Console.WriteLine("参数数量不对");
                return;
            }
            if (!File.Exists(args[0]))
            {
                Console.WriteLine("文件不存在：{0}", args[0]);
            }

            using (var input = File.Open(args[0], FileMode.Open, FileAccess.ReadWrite))
            {
                using (var reader = new BinaryReader(input))
                {
                    input.Position = 60;
                    int offset;
                    input.Position = offset = reader.ReadInt32();

                    input.Position += 4;
                    short machine = reader.ReadInt16();
                    short sum = reader.ReadInt16();
                    input.Position += 12;
                    int size = reader.ReadInt16();
                    input.Position += 2;

                    input.Position += machine == 0x14c ? 96 : 112;
                    input.Position += 16;
                    int rva = reader.ReadInt32();

                    offset += 24 + size;
                    input.Position = offset;

                    do
                    {
                        input.Position += 8;
                        int ram = reader.ReadInt32();
                        int va = reader.ReadInt32();
                        input.Position += 4;
                        int map = reader.ReadInt32();
                        input.Position += 16;

                        if (rva >= va && rva < va + ram)
                        {
                            input.Position = rva - va + map;
                            int rvaData = FindPosition(input, reader, rva, va, map, 0, 16);
                            if (rvaData > 0)
                            {
                                input.Position = rva - va + map + rvaData;

                                rva = reader.ReadInt32();
                                size = reader.ReadInt32();
                                input.Position = rva - va + map;

                                byte[] data = reader.ReadBytes(size);
                                input.Position -= size;
                                long position = input.Position;

                                for (int i = 1; i < args.Length; i += 2)
                                {
                                    if (args[i].Length != 2)
                                    {
                                        Console.WriteLine("异常参数：{0}", args[i]);
                                        return;
                                    }
                                    switch (args[i][0])
                                    {
                                        case '/':
                                        case '-':
                                            break;
                                        default:
                                            Console.WriteLine("异常参数：{0}", args[i]);
                                            return;
                                    }

                                    switch (args[i][1])
                                    {
                                        case 'f':
                                        case 'F':
                                            UpdateFileVersion(input, args[i + 1]);
                                            break;
                                        case 'p':
                                        case 'P':
                                            UpdateResourceData(input, data, "ProductVersion", args[i + 1], 2);
                                            break;
                                        case 'c':
                                        case 'C':
                                            UpdateResourceData(input, data, "CompanyName", args[i + 1]);
                                            break;
                                        case 'd':
                                        case 'D':
                                            UpdateResourceData(input, data, "FileDescription", args[i + 1]);
                                            break;
                                        case 'n':
                                        case 'N':
                                            UpdateResourceData(input, data, "ProductName", args[i + 1]);
                                            break;
                                        case 'l':
                                        case 'L':
                                            UpdateResourceData(input, data, "LegalCopyright", args[i + 1], 2);
                                            break;
                                        case 't':
                                        case 'T':
                                            UpdateResourceData(input, data, "LegalTrademarks", args[i + 1]);
                                            break;
                                        default:
                                            Console.WriteLine("不支持的参数：{0}", args[i]);
                                            return;
                                    }

                                    input.Position = position;
                                }

                                input.Flush();
                                return;
                            }
                        }
                        sum--;
                    } while (sum > 0);
                }
            }
        }

        static int FindPosition(FileStream input, BinaryReader reader, int rva, int va, int map, int off, int flag)
        {
            input.Position += 12;
            int loop = reader.ReadInt16() + reader.ReadInt16();
            for (int i = 0; i < loop; i++)
            {
                input.Position = rva - va + map + off + 16 + i * 8;

                input.Position += 4;
                int offset = reader.ReadInt32();
                int offValue = offset & 0x7fffffff;
                if ((offset & 0x80000000) != 0)
                {
                    input.Position -= 8;
                    offset = reader.ReadInt32();
                    input.Position += 4;

                    if ((offset & 0x80000000) != 0) continue;
                    if (flag != 0 && (offset & 0xffff) != flag) continue;

                    input.Position = rva - va + map + offValue;
                    return FindPosition(input, reader, rva, va, map, offValue, 0);
                }
                else return offValue;
            }

            return 0;
        }

        static bool UpdateResourceData(FileStream stream, byte[] data, string field, string value, int offset = 4)
        {
            var code = Encoding.Unicode;
            var fields = code.GetBytes(field);

            bool found;
            for (int i = 0; i < data.Length; i++)
            {
                found = true;
                for (int j = 0; j < fields.Length; j++)
                {
                    if (data[i + j] != fields[j])
                    {
                        found = false;
                        break;
                    }
                }

                if (found)
                {
                    int start = i + fields.Length + offset;
                    int len = 0;
                    while (data[start + len] != 0 || data[start + len + 1] != 0) len += 2;
                    string fv = code.GetString(data, start, len);
                    while (data[start + len] == 0 && data[start + len + 1] == 0) len += 2;
                    len -= 2;

                    byte[] values = code.GetBytes(value);
                    if (values.Length > len) return false;

                    Console.WriteLine("{0}：{1} → {2}", field, fv, value);

                    byte[] fvs = new byte[len];
                    Buffer.BlockCopy(values, 0, fvs, 0, values.Length);
                    stream.Position += i + fields.Length + offset;
                    stream.Write(fvs, 0, len);

                    return true;
                }
            }

            return false;
        }

        static void UpdateFileVersion(FileStream stream, string value)
        {
            ushort[] vers;
            try
            {
                vers = (from i in value.Split('.') select ushort.Parse(i)).ToArray();
                if (vers.Length > 4)
                {
                    Console.WriteLine("文件版本只能有 4 段，且每段最大值为 65535！");
                    return;
                }
                if (vers.Length != 4)
                {
                    ushort[] tmp = new ushort[4];
                    Buffer.BlockCopy(vers, 0, tmp, 0, vers.Length);
                    vers = tmp;
                }
            }
            catch { return; }

            stream.Position += 48;
            byte[] data = new byte[8];
            stream.Read(data, 0, 8);

            ushort[] ver = new ushort[4];
            Buffer.BlockCopy(data, 0, ver, 0, 8);

            Console.WriteLine("文件版本：{0}.{1}.{2}.{3} → {4}.{5}.{6}.{7}", ver[1], ver[0], ver[3], ver[2], vers[0], vers[1], vers[2], vers[3]);

            ver[1] = vers[0];
            ver[0] = vers[1];
            ver[3] = vers[2];
            ver[2] = vers[3];

            Buffer.BlockCopy(ver, 0, data, 0, 8);
            stream.Position -= 8;
            stream.Write(data, 0, 8);
        }
    }

    // 更不正规的方式
    /*
     * 注意：版本信息资源中的字符串采用 Unicode 编码
     * 
     * 1、读取文件，找到 VERSION_INFO 位置
     * 2、计算偏移位置 和 可替换长度
     * 3、用新的内容替换
     * 
     * 最后 2 个步骤可参考 UpdateResourceData、UpdateFileVersion
     * 
     */
}
